#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <time.h>
#include <netdb.h>

#include "ndmp.h"
#include "err.h"

int do_ndmp_proc ( struct ndmp_session_detail *nh, ndmp_message message, bool_t (*out_proc)(), bool_t (*in_proc)(), void *ptr1, void *ptr2)
{

  int z;

  if ( nh->status < STATE_AUTHENTICATED ) { return STAT+nh->status; };

  if ((z=prepare_ndmp_header(nh, message)) != 0 ) { return z; }

  if ( ! out_proc( &(nh->x_stream), ptr1) ) { return GEN_ERR_XDR_ERROR; }

  if ( !xdrrec_endofrecord ( &(nh->x_stream), TRUE) ) { return GEN_ERR_XDR_ERROR; }

  if ( (z=recieve_ndmp_header( nh )) != 0 ) {return z;}

  if ( ! in_proc( &(nh->x_stream), ptr2) ) { return GEN_ERR_XDR_ERROR; }

  return GEN_OK;

}

int do_ndmp_proc1 ( struct ndmp_session_detail *nh, ndmp_message message, bool_t (*in_proc)(), void *ptr1)
{

  int z;


  if ( nh->status < STATE_AUTHENTICATED ) { return STAT+nh->status; };

  if ((z=prepare_ndmp_header(nh, message)) != 0 ) { return z; }

  if ( !xdrrec_endofrecord ( &(nh->x_stream), TRUE) ) { return GEN_ERR_XDR_ERROR; }

  if ( (z=recieve_ndmp_header( nh )) != 0 ) {return z;}

  if ( ! in_proc( &(nh->x_stream), ptr1) ) { return GEN_ERR_XDR_ERROR; }

  return GEN_OK;

}

int device_close( struct ndmp_session_detail *nh, ndmp_message message )
{

  ndmp_generic_reply gr;
  int z;

  if ( nh->status < STATE_DEVICE_OPEN ) { return STAT+nh->status; }
  if ( (z=prepare_ndmp_header(nh, message)) != 0 ) { return z; }

  if ( !xdrrec_endofrecord ( &(nh->x_stream), TRUE) ) { return GEN_ERR_XDR_ERROR; }

  if ( (z=recieve_ndmp_header( nh )) != 0 ) {return z;}

  if ( ! xdr_ndmp_generic_reply (&(nh->x_stream), &gr) ) { return GEN_ERR_XDR_ERROR; }

  if ( gr.error == 0 ) { nh->status=STATE_AUTHENTICATED; }

    return NDMP_ERROR(gr.error);

}
  
  

int device_open( struct ndmp_session_detail *nh, char *device, ndmp_message message, ndmp_tape_open_mode mode)
{


  ndmp_tape_open_request open_request;
  ndmp_scsi_open_request scsi_open_request;

  ndmp_generic_reply gr;

  int z;

  if ( nh->status != STATE_AUTHENTICATED ) { return STAT+nh->status; }

  if ( (z=prepare_ndmp_header(nh, message)) != 0 ) { return z; }



  open_request.device=device;
  scsi_open_request.device=device;
  open_request.mode=mode;

  if ( message == NDMP_SCSI_OPEN ) {
    if ( ! xdr_ndmp_scsi_open_request(&(nh->x_stream), &scsi_open_request) ) { return GEN_ERR_XDR_ERROR; }
  }
  else {
    if ( ! xdr_ndmp_tape_open_request(&(nh->x_stream), &open_request) ) { return GEN_ERR_XDR_ERROR; }
  }

  if ( !xdrrec_endofrecord ( &(nh->x_stream), TRUE) ) { return GEN_ERR_XDR_ERROR; }

  if ( (z=recieve_ndmp_header( nh )) != 0 ) {return z;}

  if ( ! xdr_ndmp_generic_reply (&(nh->x_stream), &gr) ) { return GEN_ERR_XDR_ERROR; }

  if ( ! gr.error ) nh->status =  STATE_DEVICE_OPEN;
  return NDMP_ERROR(gr.error);

}


int get_host_info( struct ndmp_session_detail *nh, ndmp_config_get_host_info_reply *host_info)
{

  int z;
  if ( nh->status < STATE_AUTHENTICATED ) { return STAT+nh->status; }

  host_info->hostname=NULL;
  host_info->os_type=NULL;
  host_info->os_vers=NULL;
  host_info->hostid=NULL;


  if ( (z=prepare_ndmp_header( nh, NDMP_CONFIG_GET_HOST_INFO )) != 0 ) { return z; }

  if ( !xdrrec_endofrecord ( &(nh->x_stream), TRUE) ) { return GEN_ERR_XDR_ERROR; }

  if ( (z=recieve_ndmp_header( nh )) != 0 ) {return z;}

  if ( ! xdr_ndmp_config_get_host_info_reply (&(nh->x_stream), host_info) ) { return GEN_ERR_XDR_ERROR; }

  return NDMP_ERROR(host_info->error);

}

  
  
int init_ndmp_handle (struct ndmp_session_detail *nh) 
{

  struct sockaddr_in server_addr;
  struct hostent *ht;


   memset(&server_addr, 0, sizeof(struct sockaddr_in));
   server_addr.sin_family=AF_INET;
   server_addr.sin_port=htons(10000);

   if ( ! (ht=gethostbyname(nh->ndmp_server) )) return NET_ERROR(1); 

   if ( (server_addr.sin_addr.s_addr=inet_addr(inet_ntoa( *( struct in_addr*)( ht -> h_addr_list[0]))) ) == -1 ) return NET_ERROR(1);

   strcpy(nh->ndmp_server, (char *)inet_ntoa( *( struct in_addr*)( ht -> h_addr_list[0])));

   if ( nh->status != STATE_UNINITIALIZED ) return STAT+nh->status;

   nh->status=STATE_UNINITIALIZED; //Connection Dead;


   if ( (nh->s=ndmp_connection( & server_addr )) < 0 ) {
      if ( nh-> s == -1 ) {
	      return NET_ERROR(2);
      }
      else {
	      return NET_ERROR(3);
      }
   }

   nh->status= STATE_TCP_CONNECTED; //Connection Established

   xdrrec_create (& (nh->x_stream) , 0, 0, (char *) nh, read_stream, write_stream);

   nh->n_header.sequence=0;
   nh->n_header.reply_sequence=0;

   return GEN_OK;

}

int do_ndmp_authenticate ( struct ndmp_session_detail *nh ) 
{

   ndmp_notify_connection_status_post connected_status;
   ndmp_connect_client_auth_request auth_request;
   ndmp_generic_reply gr;
   int i;

   int z;

   if ( nh->status < STATE_TCP_CONNECTED  ) { return STAT+nh->status; }

   connected_status.text_reason=NULL;

   if ( nh->status ==  STATE_TCP_CONNECTED ) {

	   nh->x_stream.x_op=XDR_DECODE;

	   if ( ! xdrrec_skiprecord( &(nh->x_stream) )) { return GEN_ERR_XDR_ERROR; }

	   if ( ! xdr_ndmp_header( &(nh->x_stream), &(nh->r_header)) ) { return GEN_ERR_XDR_ERROR; }

	   if ( nh->r_header.message_code != NDMP_NOTIFY_CONNECTION_STATUS ) {
		nh->status=STATE_UNINITIALIZED; shutdown(nh->s,2);return GEN_ERR_SERVER_NO_RESPONSE;
           }

	   if ( ! xdr_ndmp_notify_connection_status_post( &(nh->x_stream), &connected_status) ) { return GEN_ERR_XDR_ERROR; }


	   if ( connected_status.reason != NDMP_CONNECTED ) { nh->status=STATE_UNINITIALIZED; shutdown(nh->s,2); GEN_ERR_SERVER_REJECTED; }

	   nh->status = STATE_CONNECTION_ACCEPTED ;
   }


   if (( z= prepare_ndmp_header(nh,NDMP_CONNECT_CLIENT_AUTH) ) != 0 ) { return z;}

   auth_request.auth_id=nh->user;
   auth_request.auth_password=nh->passwd;

	
   auth_request.auth_type=NDMP_AUTH_TEXT;
 
   if ( ! xdr_ndmp_connect_client_auth_request( &(nh->x_stream), &auth_request) ) { return GEN_ERR_XDR_ERROR; }


   if ( !xdrrec_endofrecord ( &(nh->x_stream), TRUE) ) { return GEN_ERR_XDR_ERROR; }

   if (( z= recieve_ndmp_header(nh)) != 0 ) { return z; }

   if ( ! xdr_ndmp_generic_reply (&(nh->x_stream), &gr) ) { return GEN_ERR_XDR_ERROR; }

   if ( gr.error != 0 ) { nh->status=STATE_CONNECTION_ACCEPTED ; return NDMP_ERROR(gr.error); }

   nh->status = STATE_AUTHENTICATED;

   xdr_free((xdrproc_t)xdr_ndmp_notify_connection_status_post, (char *) & connected_status);

   return GEN_OK;
       

}

int prepare_ndmp_header( struct ndmp_session_detail *nh, ndmp_message message)
{

  nh->x_stream.x_op=XDR_ENCODE;

  nh->n_header.sequence++;
  nh->n_header.time_stamp=nh->r_header.time_stamp;
  nh->n_header.message_type=NDMP_MESSAGE_REQUEST;
  nh->n_header.message_code=message;
  nh->n_header.reply_sequence=0;
  nh->n_header.error_code=0;

  if ( ! xdr_ndmp_header( &(nh->x_stream), &(nh->n_header)) ) { return GEN_ERR_XDR_ERROR;}

  return GEN_OK;

}

int recieve_ndmp_header(struct ndmp_session_detail *nh) 
{

  int z;
  nh->x_stream.x_op=XDR_DECODE;

  do {

    if ( ! xdrrec_skiprecord( &(nh->x_stream) )) { return GEN_ERR_XDR_ERROR; }
    if ( ! xdr_ndmp_header( &(nh->x_stream), &(nh->r_header)) ) { return GEN_ERR_XDR_ERROR; }

    if ( nh->r_header.error_code ) return NDMP_ERROR(nh->r_header.error_code);

//    if ( nh->status >=STATE_AUTHENTICATED ) if ( (z=handle_ndmp_async_messages(nh)) ) return z;

  } while ( nh->r_header.reply_sequence !=  nh->n_header.sequence) ;

  return GEN_OK;

}


int ndmp_connection (struct sockaddr_in *saddr)
{

	int s;
  	fd_set rfd,wfd;
  	struct timeval timeout;
  	int opts;

	if ( (s=socket(AF_INET, SOCK_STREAM, 0)) == -1 )  {
		return -2;
	}

	opts = fcntl(s,F_GETFL);
 	if (opts < 0) { return -1;}

	opts = (opts | O_NONBLOCK);
 	if (fcntl(s,F_SETFL,opts) < 0) { return -1; }



	FD_ZERO(&wfd);
	FD_ZERO(&rfd);
	FD_SET(s,&wfd);
	FD_SET(s,&rfd);

	timeout.tv_sec  = 10;
	timeout.tv_usec = 0;

	if ( (connect(s, (struct sockaddr *)saddr, sizeof(struct sockaddr_in))) == -1 ) {
     	    if ( (select (s+1, &rfd, &wfd, NULL, &timeout)) != 1) { return -1; }
	}

	return s;
}


int read_stream ( char *nh, char *buf, int len)  
{

	struct ndmp_session_detail *n;
 	fd_set rfd;
	struct timeval timeout;



	timeout.tv_sec=300;
	timeout.tv_usec=0;

        n=(struct ndmp_session_detail *)nh;

        if ( n->status < STATE_TCP_CONNECTED ) { return -1; }

	FD_ZERO(&rfd);
	FD_SET(n->s,&rfd);

	if ( select (n->s+1, &rfd, NULL, NULL, &timeout) != 1) {
	   printf("Read timed out! May be a network issue or not able to contact the NDMP server, do'nt be surpised if the program faults\n");
           return -1;
 	}

	return ( read (n->s, buf, len ));
        

}

int write_stream ( char *nh, char *buf, int len)  
{

	struct ndmp_session_detail *n;
        fd_set wfd;
	struct timeval timeout;

	timeout.tv_sec=300;
	timeout.tv_usec=0;

        n=(struct ndmp_session_detail *)nh;
        
        if ( n->status < STATE_TCP_CONNECTED  ) { return -1; }


        FD_ZERO(&wfd);
        FD_SET(n->s,&wfd);

        if ( select (n->s+1, NULL, &wfd, NULL, &timeout) != 1 ) {
	  printf("Write timed out! May be a network issue or not able to contact the NDMP server, do'nt be surpised if the program faults\n");
          return -1;
        }

	return ( write(n->s, buf, len ) );

}

int get_device_info( struct ndmp_session_detail *nh, ndmp_config_get_device_info_reply *device_info, ndmp_message message)
{

  int z;
  if ( nh->status < STATE_AUTHENTICATED ) { return STAT+nh->status; }

  device_info->scsi_info.scsi_info_val=NULL;
  
  if ((z=prepare_ndmp_header( nh, message )) != 0 ) { return z; }
  if ( !xdrrec_endofrecord ( &(nh->x_stream), TRUE) ) { return GEN_ERR_XDR_ERROR; }

  if ((z=recieve_ndmp_header( nh )) != 0 ) {return z;}

  if (  ! xdr_ndmp_config_get_device_info_reply( &(nh->x_stream), device_info) ) { return GEN_ERR_XDR_ERROR;}

  return NDMP_ERROR(device_info->error);

}


